// BitmapFont.cs
// Bitmap Font class for XNA
// Copyright 2006 Microsoft Corp.
// Gary Kacmarcik (garykac@microsoft.com)
// Revision: 2006-Nov-18

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Xml;
using SlimDX;
using SlimDX.Direct3D9;
using Barrage.Global.Vertex;
using Barrage.Global;
using System.Globalization;
using Barrage.Textures;

namespace Barrage.Meshes.Text
{
    /// <summary>
    /// Character draw information for text meshes
    /// </summary>
    public struct CharInfo
    {
        /// <summary>
        /// Char value
        /// </summary>
        public int Id;
        /// <summary>
        /// X Position in the font texture
        /// </summary>
        public int X;
        /// <summary>
        /// Y Position in the font texture
        /// </summary>
        public int Y;
        /// <summary>
        /// Width in the font texture
        /// </summary>
        public int Width;
        /// <summary>
        /// Height in the font texture
        /// </summary>
        public int Height;
        /// <summary>
        /// X Offset used to render the char
        /// </summary>
        public int XOffset;
        /// <summary>
        /// Y Offset used to render the char
        /// </summary>
        public int YOffset;
        /// <summary>
        /// Pixels the cursor will advance after drawing this char
        /// </summary>
        public int XAdvance;
    }
    /// <summary>
    /// Font draw information used for text meshes
    /// </summary>
    public struct FontInfo
    {
        /// <summary>
        /// Line height in pixels
        /// </summary>
        public int LineHeight;
        /// <summary>
        /// Baseline height
        /// </summary>
        public int BaseHeight;
        /// <summary>
        /// X Scale
        /// </summary>
        public int ScaleW;
        /// <summary>
        /// Y Scale
        /// </summary>
        public int ScaleH;
    }
    /// <summary>
    /// Represents a text font which gets the letters representation from a texture
    /// </summary>
    public class BitmapFont:IDisposable
	{
        const string XML_FONT_ELEMENT = "font";
        const string XML_COMMON_ELEMENT = "common";
        const string XML_CHARS_ELEMENT = "chars";
        const string XML_LINEHEIGHT_ATTRIBUTE = "lineHeight";
        const string XML_BASE_ATTRIBUTE = "base";
        const string XML_SCALEW_ATTRIBUTE = "scaleW";
        const string XML_SCALEH_ATTRIBUTE = "scaleH";
        const string XML_PAGES_ELEMENT = "pages";
        const string XML_FILE_ATTRIBUTE = "file";
        
        const string XML_CHAR_ELEMENT = "char";
        const string XML_ID_ATTRIBUTE = "id";
        const string XML_X_ATTRIBUTE = "x";
        const string XML_Y_ATTRIBUTE = "y";
        const string XML_WIDTH_ATTRIBUTE = "width";
        const string XML_HEIGHT_ATTRIBUTE = "height";
        const string XML_XOFFSET_ATTRIBUTE = "xoffset";
        const string XML_YOFFSET_ATTRIBUTE = "yoffset";
        const string XML_XADVANCE_ATTRIBUTE = "xadvance";

        private Dictionary<char, CharInfo> charInfo = new Dictionary<char, CharInfo>();
        /// <summary>
        /// Dictionary holding information about each glyph in the bitmap
        /// </summary>
        public Dictionary<char, CharInfo> CharInfo { get { return charInfo; } }
        private FontInfo fontInfo;
        /// <summary>
        /// Retrieves information about the font
        /// </summary>
        public FontInfo FontInfo { get { return fontInfo; } }
        private Texture fontTexture;
        /// <summary>
        /// Font's bitmap texture
        /// </summary>
        public Texture Texture { get { return fontTexture; } }

        /// <summary>
        /// Constructor. Loads the font from an xml file
        /// </summary>
        /// <param name="name">XML file path</param>
        public BitmapFont(string name)
        {
            processFile(Store.PathConfig[Store.FONT_PATH] + name + ".xml");
        }

        /// <summary>
        /// Calculate the width of the given string.
        /// </summary>
        /// <param name="format">String format</param>
        /// <param name="args">String format arguments</param>
        /// <returns>Width (in pixels) of the string</returns>
        public int MeasureString(string format, params object[] args)
        {
            string str = string.Format(format, args);
            int pxWidth = 0;
            
            foreach (char c in str)
            {
                if (!charInfo.ContainsKey(c))
                {
                    //TODO: print out undefined char glyph
                    continue;
                }

                CharInfo cinfo = charInfo[c];

                // update the string width
                pxWidth += cinfo.XAdvance;
            }

            return pxWidth;
        }

        /// <summary>
        /// Calculate the number of characters that fit in the given width.
        /// </summary>
        /// <param name="pxMaxWidth">Maximum string width</param>
        /// <param name="str">String</param>
        /// <param name="nChars">Number of characters that fit</param>
        /// <param name="pxWidth">Width of substring</param>
        public void CountCharWidth(int pxMaxWidth, string str, out int nChars, out int pxWidth)
        {
            int nLastWordBreak = 0;
            int pxLastWordBreakWidth = 0;
            int pxLastWidth = 0;
            
            nChars = 0;
            pxWidth = 0;

            foreach (char c in str)
            {
                // if this is a newline, then return. the width is set correctly
                if (c == '\n')
                {
                    nChars++;
                    return;
                }

                if (!charInfo.ContainsKey(c))
                {
                    // Skip the char we can't render
                    nChars++;
                    continue;
                }

                CharInfo cinfo = charInfo[c];

                // update the string width and char count
                pxLastWidth = pxWidth;
                pxWidth += cinfo.XAdvance;
                nChars++;

                // record the end of the previous word if this is a whitespace char
                if (Char.IsWhiteSpace(c))
                {
                    nLastWordBreak = nChars;			// include space in char count
                    pxLastWordBreakWidth = pxLastWidth;	// don't include space in width
                }

                // if we've exceeded the max, then return the chars up to the last complete word
                if (pxWidth > pxMaxWidth)
                {
                    pxWidth = pxLastWordBreakWidth;
                    if (pxWidth == 0)
                    {
                        // fallback to last char if we haven't seen a complete word
                        pxWidth = pxLastWidth;
                        nChars--;
                    }
                    else
                        nChars = nLastWordBreak;
                    if (nChars == 0) nChars = 1;
                    return;
                }
            }
        }

        private void processFile(string file)
        {
            XmlDocument doc = new XmlDocument();
            doc.Load(file);
            
            LoadFont(doc.ChildNodes);

			
        }

        private void LoadFont(XmlNodeList xnl)
		{
            foreach (XmlNode xn in xnl)
			{
				if (xn.Name == XML_FONT_ELEMENT)
				{
                    foreach (XmlNode fxn in xn.ChildNodes)
                    {
                        switch (fxn.Name)
                        {
                            //case XML_INFO_ELEMENT:
                            case XML_COMMON_ELEMENT:
                                LoadCommonInfo(fxn);
                                break;
                            case XML_PAGES_ELEMENT:
                                LoadPage(fxn);
                                break;
                            case XML_CHARS_ELEMENT:
                                LoadCharInfo(fxn.ChildNodes);
                                break;
                        }
                    }					
				}
			}
		}

        private void LoadPage(XmlNode n)
        {
            if (n.FirstChild == null) return;
            string name = XmlHelper.GetXMLAttribute(n.FirstChild, XML_FILE_ATTRIBUTE);
            fontTexture = TextureManager.LoadTexture(Store.PathConfig[Store.FONT_TEXTURE_PATH]+name);
        }

        private void LoadCommonInfo(XmlNode xn)
        {
            fontInfo = new FontInfo();
            fontInfo.LineHeight = ParseInt(XmlHelper.GetXMLAttribute(xn, XML_LINEHEIGHT_ATTRIBUTE), 32);
            fontInfo.BaseHeight = ParseInt(XmlHelper.GetXMLAttribute(xn, XML_BASE_ATTRIBUTE), 16);
            fontInfo.ScaleW = ParseInt(XmlHelper.GetXMLAttribute(xn, XML_SCALEW_ATTRIBUTE), 20);
            fontInfo.ScaleH = ParseInt(XmlHelper.GetXMLAttribute(xn, XML_SCALEH_ATTRIBUTE), 20);            
        }

        private void LoadCharInfo(XmlNodeList xnl)
        {
            foreach (XmlNode n in xnl)
            {
                if (n.Name != XML_CHAR_ELEMENT) continue;

                AddChar(n);
            }
        }

        private void AddChar(XmlNode n)
        {
            CharInfo ci=new CharInfo();
            ci.Id = ParseInt(XmlHelper.GetXMLAttribute(n, XML_ID_ATTRIBUTE), 0);
            ci.X = ParseInt(XmlHelper.GetXMLAttribute(n, XML_X_ATTRIBUTE), 0);
            ci.Y = ParseInt(XmlHelper.GetXMLAttribute(n, XML_Y_ATTRIBUTE), 0);
            ci.Width = ParseInt(XmlHelper.GetXMLAttribute(n, XML_WIDTH_ATTRIBUTE), 0);
            ci.Height = ParseInt(XmlHelper.GetXMLAttribute(n, XML_HEIGHT_ATTRIBUTE), 0);
            ci.XOffset = ParseInt(XmlHelper.GetXMLAttribute(n, XML_XOFFSET_ATTRIBUTE), 0);
            ci.YOffset = ParseInt(XmlHelper.GetXMLAttribute(n, XML_YOFFSET_ATTRIBUTE), 0);
            ci.XAdvance = ParseInt(XmlHelper.GetXMLAttribute(n, XML_XADVANCE_ATTRIBUTE), 0);


            charInfo.Add((char)ci.Id, ci);
        }
        
        private static int ParseInt(string text, int defaultValue)
        {
            int v;
            if (int.TryParse(text, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out v)) return v;
            return defaultValue;
        }

        private static float ParseFloat(string text, float defaultValue)
        {
            float v;
            if (Single.TryParse(text, NumberStyles.Float, NumberFormatInfo.InvariantInfo, out v)) return v;
            return defaultValue;
        }

        /// <summary>
        /// Clear resources
        /// </summary>
        public void Dispose()
        {
            charInfo.Clear();
            fontTexture.Dispose();
            fontTexture = null;
            NLog.LogManager.GetLogger(ToString()).Debug("Disposing");
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Destructor. Clear resources
        /// </summary>
        ~BitmapFont()
        {
            Dispose();
        }

	}
}
